#! /usr/bin/env python
# -*- coding: utf-8 -*-

'''
flat => tree

方針
ファイルサイズはそこまで大きくないはずなので (大きくても 1M を想定) DOM を使おう

<!-- 入力イメージ -->
<root>
  <item>
    <detail/>
    <group>
      <gitem/>
      <gitem/>
    </group>
    <group/>
  </item>
  <item />
  <item />
</root>

<!-- 出力イメージ -->
<root>
  <items>
    <item id="" name="" file=""/>
  </items>
  <tree>
    <gitem>
      <gitem>
        <item id=""/>
      </gitem>
      <gitem>
        <item id=""/>
      </gitem>
    </gitem>
  </tree>
</root>

エレメントの比較は clone(false) で複製したノードに対して toxml() を呼んで文字列にして行う。
入力ファイル内の優先度は items の並びで表現する
一つのitemの中に複数 group がある時は同じ item の id を参照
同じ名前で複数itemがある時は異なる id を参照する
'''

import codecs
import xml.dom.minidom as MiniDom
import xml.dom as Dom

# エレメントの名前 EN_xxxx
# 属性の名前 AN_xxxx
EN_IN_ROOT = 'root'		# ルートエレメント名
EN_IN_ITEM = 'item'		# 要素毎のルートエレメント名
AN_IN_NAME = 'name'
AN_IN_NO = 'no'
EN_IN_DETAIL = 'detail'	# 要素の説明をのせるエレメント名
EN_IN_GROUP = 'group'		# 要素の中でグループ分けする際のルートエレメント名
EN_IN_CONDITION = 'condition'		# グループの中に入る条件

EN_OUT_ROOT = 'root'
EN_OUT_ITEMS = 'items'
EN_OUT_ITEM = 'item'
AN_OUT_NAME = 'name'
AN_OUT_NO = 'no'
AN_OUT_REFID = 'refid'		# items の item と condition_tree の item をつなぐための id
AN_OUT_FILEPATH = 'filepath'
AN_OUT_DETAIL = 'detail'
EN_OUT_CONDITION_ROOT = 'condition_root'
EN_OUT_CONDITION = 'condition'
EN_OUT_ITEMREF = 'itemref'


# 引数解析
# -in 入力ファイルパス (複数指定可)
# -out 出力ファイルパス

# 入力ファイルを DOM で解析する
# item 情報抽出
# id の決定
#   gitem 抽出
# gitem の数をカウントする

#vvvv アルゴリズム要検討部分

# 数の多い順に gitem をソート
# 数の多い順に tree を作成していく

#^^^^ アルゴリズム要検討部分

# データ構造イメージ
'''
class Item:
	id
	name
	filename
	xml
	detail

class FlatGroup:
	item
	gitems[]

class TreeGrouop:
	item
	gitem
	parent
	children[]
'''

# 出力ファイルを開く(DOMで出力する？)

# item 情報出力
# tree に従い

import sys
from optparse import OptionParser

#---
class FlatDataGroup(object):
	'''itemが持つgroup情報'''

	# condition(文字列)のリスト
	conditions = []

	def __str__(self):
		'''unicode文字列を返す'''
		return ','.join(self.conditions)

	@staticmethod
	def create_from_element(element):
		'''DOM の group element から FlatDataGroup クラスのインスタンスを作成する'''
		obj = FlatDataGroup()

		condition_nodes = element.getElementsByTagName(EN_IN_CONDITION)
		for condition_node in condition_nodes:
			obj.conditions.append(condition_node.toxml())

		return obj

#---
class FlatDataItem(object):
	'''フラットにデータを持っている時の item 情報'''

	# ファイルパス
	filepath = 'no path'

	# ノードの名称
	name = 'no name'

	# 一意に特定するための id
	id = None

	# 説明文
	detail = ''

	# グループ
	# FlatDataGroup のリスト
	groups = []

	def __str__(self):
		'''unicode文字列を返す'''
		return ','.join([
						'filepath = %s' % self.filepath,
						'name = %s' % self.name,
						'id = %s' % self.id,
						'detail = %s' % self.detail,
						'group = [%s]' % ','.join(
							[str(x) for x in self.groups]
							)])

	@staticmethod
	def create_from_element(filepath, element):
		'''DOM の item element から FlatDataItem クラスのインスタンスを作成して返す'''
		obj = FlatDataItem()
		obj.filepath = filepath
		obj.name = element.tagName
		obj.id = make_id(obj.name)

		detail_nodes = element.getElementsByTagName(EN_IN_DETAIL)
		if detail_nodes != None and detail_nodes[0].firstChild != None:
			obj.detail = detail_nodes[0].firstChild.data

		group_nodes = element.getElementsByTagName(EN_IN_GROUP)
		for group_node in group_nodes:
			obj.groups.append(FlatDataGroup.create_from_element(group_node))

		return obj

#---
# Flat タイプで持っているデータ
class FlatData(object):
	'''フラットにデータを持っている時の構造'''
	items = []

	def __init__(self):
		pass

	def __str__(self):
		'''unicode文字列を返す'''
		return ('items count =  %d\nitems = [\n' % len(self.items)) + '\n'.join([str(x) for x in self.items]) + '\n]'

	def append_from_file(self, filepath):
		'''ファイルを追加読み込み'''
		doc = MiniDom.parse(filepath)
		item_nodes = doc.getElementsByTagName(EN_IN_ITEM)

		for item_node in item_nodes:
			self.append_from_item_element(filepath, item_node)

	def append_from_item_element(self, filepath, item_elm):
		'''DOM item エレメントノードから追加
		filepath は item_elm が含まれていたファイルのパス。item_elm に含まれていない情報なので別途渡す
		'''
		self.items.append(FlatDataItem.create_from_element(filepath, item_elm))

	def write_to_file(self):
		'''item情報にかかれた出力先にファイルを出力'''
		# TODO

#---
class TreeDataItem(object):
	'''item 情報'''
	# ファイルパス
	filepath = 'no path'

	# ノードの名称
	name = 'no name'

	# 一意に特定するための id
	id = None

	# 番号
	no = 0

	# 説明文
	detail = ''

	# 関係の
	leaf_conditions = []

	def __init__(self):
		'''コンストラクタ'''
		# 今の所何もしない
		
	def __str__(self):
		return ','.join([self.filepath, self.name, self.id, self.detail])

	def copy_from_flatdataitem(self, flatobj):
		'''FlatDataItem から要素を浅くコピーしてくる'''
		self.filepath = flatobj.filepath
		self.name = flatobj.name
		self.id = flatobj.id
		self.detail = flatobj.detail


	@staticmethod
	def create_from_element(filepath, element):
		'''item エレメントから作成'''
		obj = TreeDataItem()
		flatobj = FlatDataItem.create_from_element(filepath, element)
		self.copy_from_flatdataitem(flatobj)


#---
class TreeDataCondition(object):
	'''木構造の condition'''

	isroot = False
	parent = None
	children = []

	# condition(文字列)
	condition_text = None

	# ここにぶら下がる item のリスト
	items = []

	def __init__(self, isroot=False):
		'''コンストラクタ'''
		self.isroot = isroot


	def append_child(self, condition):
		'''TreeDataCondition を子に追加'''
		self.children.append(condition)
		condition.parent = self


	def append_item(self, item):
		'''TreeDataItem との関係を追加'''
		self.items.append(item)
		item.leaf_conditions.append(self)


	def is_equal(self, b):
		'''比較'''
		return self.condition_text == b.condition_text


	@staticmethod
	def create_from_element(element):
		'''condition エレメントから作成
		親子付けや item との関係追加は別途操作が必要
		'''
		obj = TreeDataCondition()
		obj.condition_text = element.toxml()
		return obj



#---
class TreeData(object):
	'''木構造でデータを持っている時の構造
	優先度は TreeData#items の順番
	条件検索時は condition にマッチする item を全て洗い出し、items リストを頭から検索して最初に
	見つかった物を返す。というノリ。
	'''

	# item情報配列
	items = []

	# condition tree のルート
	root = TreeDataCondition(isroot=True)

	def __init__(self):
		pass

	def add_item(self, item):
		self.items.append(item)

	def __str__(self):
		'''unicode文字列を返す'''
		return '[' + ','.join([
			'items = [%s]' % ','.join(map(lambda x: str(x), self.items)),
			'root = %s' % str(self.root),
			]) + ']'


def compile_condition_count(flatdata):
	'''condition(文字列)の出現回数をカウントして、マップを返す'''
	condition_count = {}

	# condition の集計を取る
	for item in flatdata.items:
		for group in item.groups:
			for condition in group.conditions:
				if condition in condition_count:
					condition_count[condition] += 1
				else:
					condition_count[condition] = 1

	return condition_count


def add_to_treedatacondition(treedataitem, flatdatagroup, condition_root, condition_count):
	'''フラットのグループ情報
	引数
		treedataitem ..... TreeDataCondition に追加する Item
		flatdatagroup .... Item に設定された条件 (FlatDataGroup)
		condition_root ... TreeDataCondition のルート
		condition_count .. 条件文字列の出現回数 (ハッシュ)
	'''
	print('add_to_treedatacondition')
	# condition(文字列)のリストを出現カウントをキーにソート
	conditions = flatdatagroup.conditions[:]
	print(conditions)

	# ソート
	conditions.sort(key=lambda x: condition_count[x])

	#
	cur = condition_root
	for str_condition in conditions:
		print(str_condition)
		# cur の子供に condition があるか調べる
		is_child = False
		found_child = None
		for child in cur.children:
			print(child)
			if str_condition == child.condition_text:
				is_child = True
				found_child = child
				break

		if is_child:
			# いれば cur はその子
			cur = found_child
		else:
			# いなければ 新しく TreeDataCondition を追加して cur 変更
			new_condition = TreeDataCondition()
			new_condition.condition_text = str_condition
			cur.append_child(new_condition)
			cur = new_condition
			print('new_condition.children = ')
			print(cur.children)

	# エラーチェック
	if cur == condition_root:
		print ('ERROR : あってはいけない状態')
		raise Exception('ERROR')

	# 最後の condition に item 追加
	cur.append_item(treedataitem)

def add_treedata_condition_children(out_doc, out_parent, in_cur):
	'''
	TreeDataCondition の child を DOM の ELEMENT に変換する
	再起的に呼び出される
	
	引数
	  out_doc ..... 出力 Document 
	  out_parent .. 親の Condition Element
	  in_cur ...... TreeDataCondition 
	'''
	print('add_treedata_condition_children({0}, {1}, {2})'.format(out_doc, out_parent, in_cur))
	
	for child in in_cur.children:
		condition_doc = MiniDom.parseString(child.condition_text)
		out_condition_elm = condition_doc.documentElement.cloneNode(deep=0)
		
		# itemref
		for item in child.items:
			out_itemref_elm = out_doc.createElement(EN_OUT_ITEMREF)
			out_itemref_elm.setAttribute(AN_OUT_REFID, item.id)

			out_condition_elm.appendChild(out_itemref_elm)
		
		# 再起的に子供を element 化
		add_treedata_condition_children(out_doc, out_condition_elm, child)
		
		# add to parent
		out_parent.appendChild(out_condition_elm)
		
	return		

def write_treedata_to_xml(treedata, filepath):
	'''TreeData を xml ファイルに出力する'''
	domimp = MiniDom.getDOMImplementation()
	doc = domimp.createDocument('', EN_OUT_ROOT, None)
	
	# <root>
	root_elm = doc.documentElement
	
	# <items>
	items_elm = doc.createElement(EN_OUT_ITEMS)
	root_elm.appendChild(items_elm)
	
	# <item>
	for item in treedata.items:
		item_elm = doc.createElement(EN_OUT_ITEM)
		item_elm.setAttribute(AN_OUT_NAME, item.name)
		item_elm.setAttribute(AN_OUT_NO, item.no)
		item_elm.setAttribute(AN_OUT_REFID, item.id)
		item_elm.setAttribute(AN_OUT_FILEPATH, item.filepath)
		item_elm.setAttribute(AN_OUT_DETAIL, item.detail)
		
		items_elm.appendChild(item_elm)
	
	# <condition_root>
	
	out_condition_root_elm = doc.createElement(EN_OUT_CONDITION_ROOT)
	
	cur = treedata.root
	out_parent_elm = out_condition_root_elm
	
	# 子供を再起的に element に変換
	add_treedata_condition_children(doc, out_parent_elm, cur)
			
	f = codecs.open(filepath, 'wt', 'utf-8')
	
	# TODO xml 出力
	doc.writexml(f, indent="  ", addindent="", newl="¥n")
	
	f.close()

def convert_flat_to_tree(flatdata):
	'''フラット => 木構造 変換/TreeData を返す'''
	# condition(文字列) をキーに出現回数をカウント
	condition_count = compile_condition_count(flatdata)

	# tree
	treedata = TreeData()
	print('treedata.root.children')
	print(len(treedata.root.children))

	# 追加していく
	for item in flatdata.items:
		treedataitem = TreeDataItem()
		treedataitem.copy_from_flatdataitem(item)
		treedata.add_item(treedataitem)
		
		for group in item.groups:
			add_to_treedatacondition(treedataitem, group, treedata.root, condition_count)

	return treedata


def convert_tree_to_flat(treedata):
	'''木構造 => フラット 変換/FlatData を返す'''
	# TODO

# make_id で使用
make_id_name_count = {}

def make_id(name):
	'''
	名前を渡して、新しいユニークな id を返す
	'''
	global make_id_name_count

	if name in make_id_name_count:
		make_id_name_count[name] += 1
	else:
		make_id_name_count[name] = 1

	result = '%s%d' % (name, make_id_name_count[name])

	return result


def main(argv):
	# 引数解析
	parser = OptionParser("sandbox converter01")
	parser.add_option('-i', '--input', action='append', type='string', dest='in_paths')
	parser.add_option('-o', '--output', action='store', type='string', dest='out_path')

	(options, args) = parser.parse_args(argv)
	if len(args) != 1 or options.in_paths == None or options.out_path == None:
		parser.print_help()
		parser.error("incorrect argument\n" + str(argv))
		return

	# 読み込み
	flatdata = FlatData()
	for inpath in options.in_paths:
		flatdata.append_from_file(inpath)

	print('flatdata\n')
	print((str(flatdata)))

	# flatdata => treedata
	treedata = convert_flat_to_tree(flatdata)

	print('treedata\n')
	print((str(treedata)))
	
	# TODO コメント削除 / 下、無限ループに入るのでコメントアウト
	# write_treedata_to_xml(treedata, options.out_path)
	
	return

if __name__ == '__main__':
	main(sys.argv)